<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
<meta name="generator" content="Doxygen 1.14.0"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>DM-CtrlH7-BF-DevProgram: dji_motor</title>
<link href="tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="dynsections.js"></script>
<script type="text/javascript" src="clipboard.js"></script>
<link href="navtree.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="navtreedata.js"></script>
<script type="text/javascript" src="navtree.js"></script>
<script type="text/javascript" src="cookie.js"></script>
<link href="search/search.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="search/searchdata.js"></script>
<script type="text/javascript" src="search/search.js"></script>
<link href="doxygen.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
 <tbody>
 <tr id="projectrow">
  <td id="projectlogo"><img alt="Logo" src="DMBF-Black.png"/></td>
  <td id="projectalign">
   <div id="projectname">DM-CtrlH7-BF-DevProgram<span id="projectnumber">&#160;beta 0.1</span>
   </div>
   <div id="projectbrief">C.ONE Studio Damiao Development Board Framework</div>
  </td>
 </tr>
 </tbody>
</table>
</div>
<!-- end header part -->
<!-- Generated by Doxygen 1.14.0 -->
<script type="text/javascript">
var searchBox = new SearchBox("searchBox", "search/",'.html');
</script>
<script type="text/javascript">
$(function() { codefold.init(); });
</script>
<script type="text/javascript" src="menudata.js"></script>
<script type="text/javascript" src="menu.js"></script>
<script type="text/javascript">
$(function() {
  initMenu('',true,false,'search.php','Search',true);
  $(function() { init_search(); });
});
</script>
<div id="main-nav"></div>
</div><!-- top -->
<div id="side-nav" class="ui-resizable side-nav-resizable">
  <div id="nav-tree">
    <div id="nav-tree-contents">
      <div id="nav-sync" class="sync"></div>
    </div>
  </div>
  <div id="splitbar" style="-moz-user-select:none;" 
       class="ui-resizable-handle">
  </div>
</div>
<script type="text/javascript">
$(function(){initNavTree('md_modules_2motor_2_d_j_imotor_2dji__motor.html','',''); });
</script>
<div id="container">
<div id="doc-content">
<!-- window showing the filter options -->
<div id="MSearchSelectWindow"
     onmouseover="return searchBox.OnSearchSelectShow()"
     onmouseout="return searchBox.OnSearchSelectHide()"
     onkeydown="return searchBox.OnSearchSelectKey(event)">
</div>

<!-- iframe showing the search results (closed by default) -->
<div id="MSearchResultsWindow">
<div id="MSearchResults">
<div class="SRPage">
<div id="SRIndex">
<div id="SRResults"></div>
<div class="SRStatus" id="Loading">Loading...</div>
<div class="SRStatus" id="Searching">Searching...</div>
<div class="SRStatus" id="NoMatches">No Matches</div>
</div>
</div>
</div>
</div>

<div><div class="header">
  <div class="headertitle"><div class="title">dji_motor </div></div>
</div><!--header-->
<div class="contents">
<div class="textblock"><p><a class="anchor" id="autotoc_md66"></a></p>
<p align="right"></p>
<p><a href="#" onclick="location.href='mai'+'lto:'+'neo'+'zn'+'g1@'+'hn'+'u.e'+'du'+'.cn'; return false;">neozn<span class="obfuscator">.nosp@m.</span>g1@h<span class="obfuscator">.nosp@m.</span>nu.ed<span class="obfuscator">.nosp@m.</span>u.cn</a></p>
<blockquote class="doxtable">
<p>TODO:</p>
<ol type="1">
<li>给不同的电机设置不同的低通滤波器惯性系数而不是统一使用宏</li>
<li>为M2006和M3508增加开环的零位校准函数 </li>
</ol>
</blockquote>
<hr  />
<blockquote class="doxtable">
<p>建议将电机的反馈频率通过RoboMaster Assistant统一设置为500Hz。当前默认的<span class="tt">MotorTask()</span>执行频率为500Hz，若不修改电机反馈频率可能导致单条总线挂载的电机数量有限，且容易出现帧错误和仲裁失败的情况。 </p>
</blockquote>
<h1 class="doxsection"><a class="anchor" id="autotoc_md68"></a>
总览和封装说明</h1>
<blockquote class="doxtable">
<p>如果你不需要理解该模块的工作原理，你只需要查看这一小节。 </p>
</blockquote>
<p>dji_motor模块对DJI智能电机，包括M2006，M3508以及GM6020进行了详尽的封装。你不再需要关心PID的计算以及CAN报文的发送和接收解析，你只需要专注于根据应用层的需求，设定合理的期望值，并通过<span class="tt">DJIMotorSetRef()</span>设置对应电机的输入参考即可。</p>
<p>**==设定值的单位==**</p>
<ol type="1">
<li>==位置环为**角度制**（0-360，total_angle可以为任意值）==</li>
<li>==速度环为角速度，单位为**度/每秒**（deg/sec）==</li>
<li>==电流环为A==</li>
<li><p class="startli">==GM6020的输入设定为**力矩**，待测量（-30000~30000）==</p>
<p class="startli">==M3508的输入设定为-20A~20A （-16384~16384）==</p>
<p class="startli">==M2006的输入设定为-10A~10A （-10000~10000）==</p>
</li>
</ol>
<p>如果你希望更改电机的反馈来源，比如进入小陀螺模式/视觉模式（这时候你想要云台保持静止，使用IMU的yaw角度值作为反馈来源），只需要调用<span class="tt">DJIMotorChangeFeed()</span>，电机便可立刻切换反馈数据来源至IMU。</p>
<p>要获得一个电机，请通过<span class="tt">DJIMotorInit()</span>并传入一些参数，他就会返回一个电机的指针。你也不再需要查看这些电机和电调的说明书，**只需要设置其电机id**（6020为拨码开关值，2006和3508为电调的闪动次数），该模块会自动为你计算CAN发送和接收ID并搞定所有硬件层的琐事。</p>
<p>初始化电机时，你需要传入的参数包括：</p>
<ul>
<li><p class="startli">**电机挂载的CAN总线设置**，CAN1 or CAN2，以及电机的id，使用<span class="tt">can_instance_config_s</span>封装，只需要设置这两个参数:</p>
<div class="fragment"><div class="line">CAN_HandleTypeDef *can_handle;</div>
<div class="line">uint32_t tx_id; <span class="comment">// tx_id设置为电机id,不需要查说明书计算，直接为电调的闪动次数或拨码开关值，为1-8</span></div>
</div><!-- fragment --></li>
<li><p class="startli">**电机类型**，使用<span class="tt">Motor_Type_e</span>：</p>
<div class="fragment"><div class="line">GM6020 = 0</div>
<div class="line">M3508  = 1</div>
<div class="line">M2006  = 2</div>
</div><!-- fragment --></li>
<li><b>电机控制设置</b><ul>
<li><p class="startli">闭环类型</p>
<div class="fragment"><div class="line">OPEN_LOOP</div>
<div class="line">CURRENT_LOOP </div>
<div class="line">SPEED_LOOP </div>
<div class="line">ANGLE_LOOP </div>
<div class="line">CURRENT_LOOP | SPEED_LOOP       <span class="comment">// 同时对电流和速度闭环</span></div>
<div class="line">SPEED_LOOP   | ANGLE_LOOP       <span class="comment">// 同时对速度和位置闭环</span></div>
<div class="line">CURRENT_LOOP | SPEED_LOOP |ANGLE_LOOP <span class="comment">// 三环全开</span></div>
</div><!-- fragment --></li>
<li><p class="startli">是否反转</p>
<p class="startli"><span class="tt">c
    MOTOR_DIRECTION_NORMAL 
    MOTOR_DIRECTION_REVERSE
    </span></p>
</li>
<li><p class="startli">是否其他反馈来源，以及他们对应的数据指针（如果有的话）</p>
<p class="startli">```c MOTOR_FEED = 0 </p>
</li>
</ul>
</li>
</ul>
<h1 class="doxsection"><a class="anchor" id="autotoc_md69"></a>
OTHER_FEED = 1</h1>
<p>// 电流只能从电机传感器获得所以无法设置其他来源 ```</p>
<ul>
<li><p class="startli">每个环的PID参数以及是否使用改进功能，以及其他反馈来源指针（如果在上一步启用了其他数据来源）</p>
<p class="startli">```c typedef struct // config parameter { float Kp; float Ki; float Kd;</p>
<p class="startli">float MaxOut; // 输出限幅 // 以下是优化参数 float IntegralLimit; // 积分限幅 float DeadBand; // 死区 float CoefA; // For Changing Integral float CoefB; // ITerm = Err*((A-abs(err)+B)/A) when B&lt;|err|&lt;A+B float Output_LPF_RC; // RC = 1/omegac float Derivative_LPF_RC;</p>
<p class="startli">PID_Improvement_e Improve; // 优化环节，定义在下一个代码块 } PIDInit_config_s; // 只有当你设启用了对应的优化环节，优化参数才会生效 ```</p>
<p class="startli"><span class="tt">c
    typedef enum
    {
        NONE      = 0b00000000,                     \ilinebr&lt;br&gt;
        Integral_Limit     = 0b00000001,           \ilinebr&lt;br&gt;
        Derivative_On_Measurement   = 0b00000010,   \ilinebr&lt;br&gt;
        Trapezoid_Intergral      = 0b00000100,       \ilinebr&lt;br&gt;
        Proportional_On_Measurement = 0b00001000, 
        OutputFilter     = 0b00010000,               \ilinebr&lt;br&gt;
        ChangingIntegrationRate  = 0b00100000,    \ilinebr&lt;br&gt;
        DerivativeFilter    = 0b01000000,            \ilinebr&lt;br&gt;
        ErrorHandle     = 0b10000000,         \ilinebr&lt;br&gt;
    } PID_Improvement_e;
    // 若希望使用多个环节的优化，这样就行：Integral_Limit |Trapezoid_Intergral|...|...
    </span></p>
<p class="startli"><span class="tt">c
    float *other_angle_feedback_ptr
    float *other_speed_feedback_ptr
    </span></p>
</li>
</ul>
<hr  />
<p>推荐的初始化参数编写格式如下：</p>
<div class="fragment"><div class="line"><a class="code hl_struct" href="struct_motor___init___config__s.html">Motor_Init_Config_s</a> config = {</div>
<div class="line">  .motor_type = M3508,  <span class="comment">// 要注册的电机为3508电机</span></div>
<div class="line">  .can_init_config = {.can_handle = &amp;hcan1, <span class="comment">// 挂载在CAN1</span></div>
<div class="line">       .tx_id = 1},          <span class="comment">// C620每隔一段时间闪动1次,设置为1</span></div>
<div class="line">  <span class="comment">// 采用电机编码器角度与速度反馈,启用速度环和电流环,不反转,最外层闭环为速度环</span></div>
<div class="line">        .controller_setting_init_config = {.angle_feedback_source = MOTOR_FEED, </div>
<div class="line">             </div>
<div class="line">            .outer_loop_type = SPEED_LOOP,</div>
<div class="line">            .close_loop_type = SPEED_LOOP | CURRENT_LOOP, </div>
<div class="line">             .speed_feedback_source = MOTOR_FEED, </div>
<div class="line">             .motor_reverse_flag = MOTOR_DIRECTION_NORMAL},</div>
<div class="line">     <span class="comment">// 电流环和速度环PID参数的设置,不采用计算优化则不需要传入Improve参数</span></div>
<div class="line">        <span class="comment">// 不使用其他数据来源(如IMU),不需要传入反馈数据变量指针</span></div>
<div class="line">  .controller_param_init_config = {.current_PID = {.Improve = 0,</div>
<div class="line">                                                         .Kp = 1,</div>
<div class="line">                                                         .Ki = 0,</div>
<div class="line">                                                         .Kd = 0,</div>
<div class="line">                                                         .DeadBand = 0,</div>
<div class="line">                                                         .MaxOut = 4000},</div>
<div class="line">           .speed_PID = {.Improve = 0,</div>
<div class="line">                                                       .Kp = 1,</div>
<div class="line">                                                       .Ki = 0,</div>
<div class="line">                                                       .Kd = 0,</div>
<div class="line">                                                       .DeadBand = 0,</div>
<div class="line">                                                       .MaxOut = 4000}}};</div>
<div class="line"> </div>
<div class="line">dji_motor_instance *djimotor = <a class="code hl_function" href="dji__motor_8h.html#ae6a2dbd0d46fc7d80c6e2339aad09969">DJIMotorInit</a>(config); <span class="comment">// 设置好参数后进行初始化并保留返回的指针</span></div>
<div class="ttc" id="adji__motor_8h_html_ae6a2dbd0d46fc7d80c6e2339aad09969"><div class="ttname"><a href="dji__motor_8h.html#ae6a2dbd0d46fc7d80c6e2339aad09969">DJIMotorInit</a></div><div class="ttdeci">DJIMotorInstance * DJIMotorInit(Motor_Init_Config_s *config)</div><div class="ttdoc">调用此函数注册一个DJI智能电机,需要传递较多的初始化参数,请在application初始化的时候调用此函数 推荐传参时像标准库一样构造initStructure然后传入此函数....</div><div class="ttdef"><b>Definition</b> dji_motor.c:159</div></div>
<div class="ttc" id="astruct_motor___init___config__s_html"><div class="ttname"><a href="struct_motor___init___config__s.html">Motor_Init_Config_s</a></div><div class="ttdef"><b>Definition</b> motor_def.h:131</div></div>
</div><!-- fragment --><hr  />
<p>要控制一个DJI电机，我们提供了2个接口：</p>
<div class="fragment"><div class="line"><span class="keywordtype">void</span> DJIMotorSetRef(dji_motor_instance *motor, <span class="keywordtype">float</span> ref);</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">void</span> DJIMotorChangeFeed(dji_motor_instance *motor, </div>
<div class="line">                        <a class="code hl_enumeration" href="motor__def_8h.html#a306d656c63a2d986e7aff57a0526395d">Closeloop_Type_e</a> loop, </div>
<div class="line">                        Feedback_Source_e type);</div>
<div class="ttc" id="amotor__def_8h_html_a306d656c63a2d986e7aff57a0526395d"><div class="ttname"><a href="motor__def_8h.html#a306d656c63a2d986e7aff57a0526395d">Closeloop_Type_e</a></div><div class="ttdeci">Closeloop_Type_e</div><div class="ttdoc">闭环类型,如果需要多个闭环,则使用或运算 例如需要速度环和电流环: CURRENT_LOOP|SPEED_LOOP</div><div class="ttdef"><b>Definition</b> motor_def.h:25</div></div>
</div><!-- fragment --><p>调用第一个并传入设定值，它会自动根据你设定的PID参数进行动作。 如果对不同闭环都有参考输入,则设置最外层的闭环(通过此函数)并将剩下的参考输入通过前馈数据指针进行设定</p>
<p>调用第二个并设定要修改的反馈环节和反馈类型，它会将反馈数据指针切换到你设定好的变量（需要在初始化的时候设置反馈指针）。</p>
<p>**如果需要获取电机的反馈数据**（如小陀螺模式需要根据麦克纳姆轮逆运动学解算底盘速度），直接通过你拥有的<span class="tt">dji_motor_instance</span>访问成员变量：</p>
<div class="fragment"><div class="line"><span class="comment">// LeftForwardMotor是一个dji_motor_instance实例</span></div>
<div class="line"><span class="keywordtype">float</span> speed=LeftForwardMotor-&gt;motor_measure-&gt;speed_rpm;</div>
<div class="line">...</div>
</div><!-- fragment --><p><em><b>现在，忘记PID的计算和发送、接收以及协议解析，专注于模块之间的逻辑交互吧。</b></em></p>
<hr  />
<h1 class="doxsection"><a class="anchor" id="autotoc_md73"></a>
代码结构</h1>
<p>.h文件内包括了外部接口和类型定义,以及模块对应的宏。c文件内为私有函数和外部接口的定义。</p>
<p>motor_def.h内包含了一些电机通用的定义。</p>
<h1 class="doxsection"><a class="anchor" id="autotoc_md74"></a>
类型定义</h1>
<div class="fragment"><div class="line"><span class="preprocessor">#define DJI_MOTOR_CNT 12</span></div>
<div class="line"><span class="preprocessor">#define SPEED_SMOOTH_COEF 0.9f    </span><span class="comment">// better to be greater than 0.85</span></div>
<div class="line"><span class="preprocessor">#define CURRENT_SMOOTH_COEF 0.98f </span><span class="comment">// this coef must be greater than 0.95</span></div>
<div class="line"> </div>
<div class="line"><span class="keyword">typedef</span> <span class="keyword">struct </span><span class="comment">/* DJI电机CAN反馈信息*/</span></div>
<div class="line">{</div>
<div class="line">    uint16_t ecd;</div>
<div class="line">    uint16_t last_ecd;</div>
<div class="line">    int16_t speed_rpm;</div>
<div class="line">    int16_t given_current;</div>
<div class="line">    uint8_t temperate;</div>
<div class="line">    int16_t total_round;</div>
<div class="line">    int32_t total_angle;</div>
<div class="line">} dji_motor_measure;</div>
<div class="line"> </div>
<div class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span></div>
<div class="line">{</div>
<div class="line">    <span class="comment">/* motor measurement recv from CAN feedback */</span></div>
<div class="line">    dji_motor_measure motor_measure;</div>
<div class="line">    <span class="comment">/* basic config of a motor*/</span></div>
<div class="line">    <a class="code hl_struct" href="struct_motor___control___setting__s.html">Motor_Control_Setting_s</a> motor_settings;</div>
<div class="line">    <span class="comment">/* controller used in the motor (3 loops)*/</span></div>
<div class="line">    <a class="code hl_struct" href="struct_motor___controller__s.html">Motor_Controller_s</a> motor_controller;</div>
<div class="line">    <span class="comment">/* the CAN instance own by motor instance*/</span></div>
<div class="line">    can_instance motor_can_instance;</div>
<div class="line">    <span class="comment">/* sender assigment*/</span></div>
<div class="line">    uint8_t sender_group;</div>
<div class="line">    uint8_t message_num;</div>
<div class="line"> </div>
<div class="line">   uint8_t stop_flag;</div>
<div class="line">    </div>
<div class="line">    Motor_Type_e motor_type;</div>
<div class="line">} dji_motor_instance;</div>
<div class="ttc" id="astruct_motor___control___setting__s_html"><div class="ttname"><a href="struct_motor___control___setting__s.html">Motor_Control_Setting_s</a></div><div class="ttdef"><b>Definition</b> motor_def.h:73</div></div>
<div class="ttc" id="astruct_motor___controller__s_html"><div class="ttname"><a href="struct_motor___controller__s.html">Motor_Controller_s</a></div><div class="ttdef"><b>Definition</b> motor_def.h:87</div></div>
</div><!-- fragment --><ul>
<li><span class="tt">DJI_MOTOR_CNT</span>是允许的最大DJI电机数量，根据经验，暂定为每个CAN6个，防止出现拥塞。</li>
<li><span class="tt">SPEED_SMOOTH_COEF</span>和<span class="tt">CURRENT_SMOOTH_COEF</span>是电机反馈的电流和速度数据低通滤波器惯性系数，数值越小平滑效果越大，但滞后也越大。设定时不应当低于推荐值。</li>
<li><span class="tt">dji_motor_measure</span>是DJI电机的反馈信息，包括当前编码器值、上次测量编码器值、速度、电流、温度、总圈数和单圈角度。</li>
<li><p class="startli"><span class="tt"><a class="el" href="struct_motor___control___setting__s.html">Motor_Control_Setting_s</a></span>的定义在<span class="tt"><a class="el" href="motor__def_8h.html" title="电机通用的数据结构定义">motor_def.h</a></span>之中，它和<span class="tt"><a class="el" href="struct_motor___controller__s.html">Motor_Controller_s</a></span>都是所有电机通用的组件（如M3508，LK9025，HT04，MT6023等），其包含内容如下：</p>
<div class="fragment"><div class="line"><span class="keyword">typedef</span> <span class="keyword">struct </span><span class="comment">/* 电机控制配置 */</span></div>
<div class="line">{</div>
<div class="line">    <a class="code hl_enumeration" href="motor__def_8h.html#a306d656c63a2d986e7aff57a0526395d">Closeloop_Type_e</a> outer_loop_type;</div>
<div class="line">    <a class="code hl_enumeration" href="motor__def_8h.html#a306d656c63a2d986e7aff57a0526395d">Closeloop_Type_e</a> close_loop_type;</div>
<div class="line">    Motor_Reverse_Flag_e motor_reverse_flag;</div>
<div class="line">    Feedback_Source_e angle_feedback_source;</div>
<div class="line">    Feedback_Source_e speed_feedback_source;</div>
<div class="line">} <a class="code hl_struct" href="struct_motor___control___setting__s.html">Motor_Control_Setting_s</a>;</div>
</div><!-- fragment --><p class="startli"><span class="tt"><a class="el" href="struct_motor___control___setting__s.html">Motor_Control_Setting_s</a></span>里包含了电机的闭环类型，反转标志以及额外的反馈来源标志。</p><ul>
<li><p class="startli">闭环类型指示该电机使用的控制器配置，其枚举定义如下：</p>
<div class="fragment"><div class="line"><span class="keyword">typedef</span> <span class="keyword">enum</span></div>
<div class="line">{</div>
<div class="line">    CURRENT_LOOP = 0b0001,</div>
<div class="line">    SPEED_LOOP = 0b0010,</div>
<div class="line">    ANGLE_LOOP = 0b0100,</div>
<div class="line">    <a class="code hl_struct" href="struct__.html">_</a> = 0b0011,</div>
<div class="line">    __ = 0b0110,</div>
<div class="line">    ___ = 0b0111</div>
<div class="line">} <a class="code hl_enumeration" href="motor__def_8h.html#a306d656c63a2d986e7aff57a0526395d">Closeloop_Type_e</a>;</div>
<div class="ttc" id="astruct___html"><div class="ttname"><a href="struct__.html">_</a></div><div class="ttdef"><b>Definition</b> bsp_can.h:16</div></div>
</div><!-- fragment --><p class="startli">以M3508为例，假设需要进行**速度闭环**和**电流闭环**，那么在初始化时就将这个变量的值设为<span class="tt">CURRENT_LOOP | SPEED_LOOP</span>。在<span class="tt"><a class="el" href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65" title="该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率">DJIMotorControl()</a></span>中，函数将会根据此标志位判断设定的参考值需要经过那些控制器的计算。 另外,你还需要设置当前电机的最外层闭环，即电机的闭环目标为什么类型的值。初始化时需要设置<span class="tt">outer_loop_type</span>。以M2006作为拨盘电机时为例，你希望它在单发/双发等固定发射数量的模式下对位置进行闭环（拨盘转过一定角度对应拨出一颗弹丸），但你也有可能希望在连发的时候让拨盘连续的转动，以一定的频率发射弹丸。我们提供了<span class="tt">DJIMotorOuterLoop()</span>用于修改电机的外层闭环，改变电机的闭环对象。</p>
<p class="startli">&gt; 注意，务必分清串级控制（多环）和外层闭环的区别。前者是为了提高内环的性能，使得其能更好地跟随外环参考值；而后者描述的是系统真实的控制目标（闭环目标）。如3508，没有电流环仍然可以对速度完成闭环，对于高层的应用来说，它们本质上不关心电机内部是否还有电流环，它们只把外层闭环为速度的电机当作一个**速度伺服执行器**，**外层闭环**描述的就是真正的闭环目标。</p>
</li>
<li><p class="startli">为了避开恼人的正负号，提高代码的可维护性，在初始化电机时设定<span class="tt">motor_reverse_flag</span>使得所有电机都按照你想要的方向旋转，其定义如下：</p>
<p class="startli"><span class="tt">c
    typedef enum
    {
        MOTOR_DIRECTION_NORMAL = 0,
        MOTOR_DIRECTION_REVERSE = 1
    } Motor_Reverse_Flag_e;
    </span></p>
</li>
<li><p class="startli"><span class="tt">speed_feedback_source</span>以及<span class="tt">angle_feedback_source</span>是指示电机反馈来源的标志位。一般情况下电机使用自身的编码器作为控制反馈量。但在某些时候，如小陀螺模式，云台电机会使用IMU的姿态数据作为反馈数据来源。其定义如下：</p>
<p class="startli"><span class="tt">c
    typedef enum
    {
        MOTOR_FEED = 0,
        OTHER_FEED = 1
    } Feedback_Source_e;
    </span></p>
<p class="startli"><b>注意，如果启用其他数据来源，你需要在电机的控制器配置<span class="tt"><a class="el" href="struct_motor___controller__s.html">Motor_Controller_s</a></span>下的<span class="tt">other_xxx_feedback_ptr</span>中指定其他数据来源。</b></p>
<p class="startli">你可以在<span class="tt">DJIMotorChangeFeed()</span>中修改电机的数据来源。</p>
</li>
</ul>
</li>
<li><p class="startli"><span class="tt"><a class="el" href="struct_motor___controller__s.html">Motor_Controller_s</a></span>的定义也在<span class="tt"><a class="el" href="motor__def_8h.html" title="电机通用的数据结构定义">motor_def.h</a></span>之中：</p>
<div class="fragment"><div class="line"><span class="comment">/* 电机控制器,包括其他来源的反馈数据指针,3环控制器和电机的参考输入*/</span></div>
<div class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span></div>
<div class="line">{</div>
<div class="line">    <span class="keywordtype">float</span> *other_angle_feedback_ptr;</div>
<div class="line">    <span class="keywordtype">float</span> *other_speed_feedback_ptr;</div>
<div class="line">    PID_t current_PID;</div>
<div class="line">    PID_t speed_PID;</div>
<div class="line">    PID_t angle_PID;</div>
<div class="line">    </div>
<div class="line">    <span class="keywordtype">float</span> pid_ref; <span class="comment">// 将会作为每个环的输入和输出顺次通过串级闭环</span></div>
<div class="line">} <a class="code hl_struct" href="struct_motor___controller__s.html">Motor_Controller_s</a>;</div>
</div><!-- fragment --><p class="startli">两个<span class="tt">float*</span>指针应当指向其他反馈来源数据（如果有的话，需要在<span class="tt">motor_settings</span>中设定）。</p>
<p class="startli">三个PID分别为三个控制闭环所用，在<span class="tt"><a class="el" href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65" title="该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率">DJIMotorControl()</a></span>中，该函数会根据<span class="tt">close_loop_type</span>的设定计算对应的闭环。</p>
<p class="startli"><b><span class="tt">pid_ref</span>是控制的设定值，app层的应用想要更改电机的输出，就要调用<span class="tt">DJIMotorSetRef()</span>更改此值。</b></p>
</li>
<li><span class="tt">dji_motor_instance</span>是一个DJI电机实例。一个电机实例内包含电机的反馈信息，电机的控制设置，电机控制器，电机对应的CAN实例以及电机的类型；由于DJI电机支持**一帧报文控制至多4个电机**，该结构体还包含了用于给电机分组发送进行特殊处理的<span class="tt">sender_group</span>和<span class="tt">message_num</span>（具体实现细节参考<span class="tt">MotorSenderGrouping()</span>函数）。</li>
</ul>
<h1 class="doxsection"><a class="anchor" id="autotoc_md75"></a>
外部接口</h1>
<div class="fragment"><div class="line">dji_motor_instance *DJIMotorInit(can_instance_config config,</div>
<div class="line">                                 <a class="code hl_struct" href="struct_motor___control___setting__s.html">Motor_Control_Setting_s</a> motor_setting,</div>
<div class="line">                                 <a class="code hl_struct" href="struct_motor___controller___init__s.html">Motor_Controller_Init_s</a> controller_init,</div>
<div class="line">                                 Motor_Type_e type);</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">void</span> DJIMotorSetRef(dji_motor_instance *motor, <span class="keywordtype">float</span> ref);</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">void</span> DJIMotorChangeFeed(dji_motor_instance *motor, </div>
<div class="line">                        <a class="code hl_enumeration" href="motor__def_8h.html#a306d656c63a2d986e7aff57a0526395d">Closeloop_Type_e</a> loop, </div>
<div class="line">                        Feedback_Source_e type);</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">void</span> <a class="code hl_function" href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65">DJIMotorControl</a>();</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">void</span> DJIMotorStop(dji_motor_instance *motor);</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">void</span> DJIMotorEnable(dji_motor_instance *motor);</div>
<div class="line"> </div>
<div class="line"><span class="keywordtype">void</span> DJIMotorOuterLoop(dji_motor_instance *motor);</div>
<div class="ttc" id="adji__motor_8h_html_a550e8d4dd6e722e66552563550191c65"><div class="ttname"><a href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65">DJIMotorControl</a></div><div class="ttdeci">void DJIMotorControl()</div><div class="ttdoc">该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率</div><div class="ttdef"><b>Definition</b> dji_motor.c:233</div></div>
<div class="ttc" id="astruct_motor___controller___init__s_html"><div class="ttname"><a href="struct_motor___controller___init__s.html">Motor_Controller_Init_s</a></div><div class="ttdoc">电机控制器初始化结构体,包括三环PID的配置以及两个反馈数据来源指针 如果不需要某个控制环,可以不设置对应的pid config 需要其他数据来源进行反馈闭环,不仅要设置这里的指针还需要在Motor_C...</div><div class="ttdef"><b>Definition</b> motor_def.h:117</div></div>
</div><!-- fragment --><ul>
<li><span class="tt">DJIMotorInit()</span>是用于初始化电机对象的接口，传入包括电机can配置、电机控制配置、电机控制器配置以及电机类型在内的初始化参数。**它将会返回一个电机实例指针**，你应当在应用层保存这个指针，这样才能操控这个电机。</li>
<li><span class="tt">DJIMotorSetRef()</span>是设定电机输出的接口，**在调用这个函数的时候，你可以认为你的设定值会直接转变为电机的输出**。<span class="tt"><a class="el" href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65" title="该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率">DJIMotorControl()</a></span>会帮你完成闭环计算，不用担心PID。</li>
<li><span class="tt">DJIMotorChangeFeed()</span>一般在更改云台或底盘的运动模式的时候被调用，传入要修改反馈来源的电机实例指针、要修改的闭环以及反馈来源类型。如希望切换到IMU的yaw值作为云台设定值，传入yaw轴电机实例和<span class="tt">ANGLE_LOOP</span>（位置环）、<span class="tt">OTHER_FEED</span>（启用其他数据来源）即可。当然，你需要在初始化的时候设定<span class="tt">motor_controller</span>中的 <span class="tt">other_angle_feedback_ptr</span>，使其指向yaw值的变量。</li>
<li><p class="startli"><span class="tt"><a class="el" href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65" title="该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率">DJIMotorControl()</a></span>是根据电机的配置计算控制值的函数。该函数在<span class="tt">motor_task.c</span>中被调用，应当在freeRTOS中以一定频率运行。此函数为PID的计算进行了彻底的封装，要修改电机的参考输入，请在app层的应用中调用<span class="tt">DJIMotorSetRef()</span>。</p>
<p class="startli">该函数的具体实现请参照代码，注释已经较为清晰。流程大致为：</p><ol type="1">
<li>根据电机的初始化控制配置，计算各个控制闭环</li>
<li>根据反转标志位，确定是否将输出反转</li>
<li>根据每个电机的发送分组，将最终输出值填入对应的分组buff</li>
<li>检查每一个分组，若该分组有电机，发送报文</li>
</ol>
</li>
<li><span class="tt">DJIMotorStop()</span>和<span class="tt">DJIMotorEnable()</span>用于控制电机的启动和停止。当电机被设为stop的时候，不会响应任何的参考输入。</li>
<li><span class="tt">DJIMotorOuterLoop()</span>用于修改电机的外部闭环类型，即电机的真实闭环目标。</li>
</ul>
<h1 class="doxsection"><a class="anchor" id="autotoc_md76"></a>
私有函数和变量</h1>
<p>在.c文件内设为static的函数和变量</p>
<div class="fragment"><div class="line"><span class="keyword">static</span> uint8_t idx = 0; <span class="comment">// register idx,是该文件的全局电机索引,在注册时使用</span></div>
<div class="line"><span class="keyword">static</span> dji_motor_instance *dji_motor_info[DJI_MOTOR_CNT] = {NULL};</div>
</div><!-- fragment --><p>这是管理所有电机实例的入口。idx用于电机初始化。</p>
<div class="fragment"><div class="line"><span class="preprocessor">#define PI2 (3.141592f * 2)</span></div>
<div class="line"><span class="preprocessor">#define ECD_ANGLE_COEF_DJI 3.835e-4 </span><span class="comment">// ecd/8192*pi</span></div>
</div><!-- fragment --><p>这两个宏用于在电机反馈信息中的多圈角度计算，将编码器的0~8192转化为角度表示。</p>
<div class="fragment"><div class="line"><span class="comment">/* @brief 由于DJI电机发送以四个一组的形式进行,故对其进行特殊处理,用6个(2can*3group)can_instance专门负责发送</span></div>
<div class="line"><span class="comment"> *        该变量将在 DJIMotorControl() 中使用,分组在 MotorSenderGrouping()中进行</span></div>
<div class="line"><span class="comment"> *</span></div>
<div class="line"><span class="comment"> * can1: [0]:0x1FF,[1]:0x200,[2]:0x2FF</span></div>
<div class="line"><span class="comment"> * can2: [0]:0x1FF,[1]:0x200,[2]:0x2FF */</span></div>
<div class="line"><span class="keyword">static</span> can_instance sender_assignment[6] =</div>
<div class="line">{</div>
<div class="line">        [0] = {.can_handle = &amp;hcan1, .txconf.StdId = 0x1ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},</div>
<div class="line">  ...</div>
<div class="line">        ...</div>
<div class="line">};</div>
<div class="line"> </div>
<div class="line"><span class="keyword">static</span> uint8_t sender_enable_flag[6] = {0};</div>
</div><!-- fragment --><ul>
<li>这些是电机分组发送所需的变量。注册电机时，会根据挂载的总线以及发送id，将电机分组。在CAN发送电机控制信息的时候，根据<span class="tt">sender_assignment[]</span>保存的分组进行发送，而不会使用电机实例自带的<span class="tt">can_instance</span>。</li>
<li>DJI电机共有3种分组，分别为0x1FF,0x200,0x2FF。注册电机的时候，<span class="tt">MotorSenderGrouping()</span>函数会根据发送id计算出CAN的<span class="tt">tx_id</span>（即上述三个中的一个）和<span class="tt">rx_id</span>。然后为电机实例分配用于指示其在<span class="tt">sender_assignment[]</span>中的编号的 <span class="tt">sender_group</span>和其在该发送组中的位置<span class="tt">message_num</span>（一帧报文可以发送四条控制指令，<span class="tt">message_num</span>会指定电机是这四个中的哪一个）。具体的分配请查看<span class="tt">MotorSenderGrouping()</span>的定义。</li>
<li>当某一个分组有电机注册时，该分组的索引将会在<span class="tt">sender_enable_flag</span>[]中被置1，这样，就可以避免发送没有电机注册的报文，防止总线拥塞。具体的，在<span class="tt">DecodeDJIMotor()</span>中，该函数会查看<span class="tt">sender_enable_flag[]</span>的每一个位置，确定这一组是否有电机被注册，若有则发送<span class="tt">sender_assignment[]</span>中对应位置的<span class="tt">tx_buff</span>。</li>
</ul>
<div class="fragment"><div class="line"><span class="keyword">static</span> <span class="keywordtype">void</span> IDcrash_Handler(uint8_t conflict_motor_idx, uint8_t temp_motor_idx)</div>
<div class="line"> </div>
<div class="line"><span class="keyword">static</span> <span class="keywordtype">void</span> MotorSenderGrouping(can_instance_config *config)</div>
<div class="line"> </div>
<div class="line"><span class="keyword">static</span> <span class="keywordtype">void</span> DecodeDJIMotor(can_instance *_instance)</div>
</div><!-- fragment --><ul>
<li><span class="tt">IDcrash_Handler()</span>在电机id发生冲突的时候会被<span class="tt">MotorSenderGrouping()</span>调用，陷入死循环之中，并把冲突的id保存在函数里。这样就可以通过debug确定是否发生冲突以及冲突的编号。</li>
<li><span class="tt">MotorSenderGrouping()</span>被<span class="tt">DJIMotorInit()</span>调用，他将会根据电机id计算出CAN的发送和接收ID，并根据发送ID对电机进行分组。</li>
<li><p class="startli"><span class="tt">DecodeDJIMotor()</span>是解析电机反馈报文的函数，在<span class="tt">DJIMotorInit()</span>中会将其注册到该电机实例对应的<span class="tt">can_instance</span>中（即<span class="tt">can_instance</span>的<span class="tt">can_module_callback()</span>）。这样，当该电机的反馈报文到达时，<span class="tt">bsp_can.c</span>中的回调函数会调用解包函数进行反馈数据解析。</p>
<p class="startli">该函数还会对电流和速度反馈值进行滤波，消除高频噪声；同时计算多圈角度和单圈绝对角度。</p>
<p class="startli"><b>电机反馈的电流值为说明书中的映射值，需转换为实际值。</b></p>
<p class="startli"><b>反馈的速度单位是rpm（转每分钟），转换为角度每秒。</b></p>
<p class="startli"><b>反馈的位置是编码器值（0~8191），转换为角度。</b></p>
</li>
</ul>
<h1 class="doxsection"><a class="anchor" id="autotoc_md77"></a>
使用范例</h1>
<div class="fragment"><div class="line"><span class="comment">//初始化设置</span></div>
<div class="line"><a class="code hl_struct" href="struct_motor___init___config__s.html">Motor_Init_Config_s</a> config = {</div>
<div class="line">  .motor_type = GM6020,</div>
<div class="line">  .can_init_config = {</div>
<div class="line">   .can_handle = &amp;hcan1,</div>
<div class="line">   .tx_id = 6</div>
<div class="line">        },</div>
<div class="line">  .controller_setting_init_config = {</div>
<div class="line">            .angle_feedback_source = MOTOR_FEED, </div>
<div class="line">            .outer_loop_type = SPEED_LOOP,</div>
<div class="line">            .close_loop_type = SPEED_LOOP | ANGLE_LOOP, </div>
<div class="line">            .speed_feedback_source = MOTOR_FEED, </div>
<div class="line">            .motor_reverse_flag = MOTOR_DIRECTION_NORMAL</div>
<div class="line">        },</div>
<div class="line">  .controller_param_init_config = {</div>
<div class="line">            .angle_PID = {</div>
<div class="line">                .Improve = 0, </div>
<div class="line">                .Kp = 1, </div>
<div class="line">                .Ki = 0, </div>
<div class="line">                .Kd = 0, </div>
<div class="line">                .DeadBand = 0, </div>
<div class="line">                .MaxOut = 4000}, </div>
<div class="line">            .speed_PID = {</div>
<div class="line">                .Improve = 0, </div>
<div class="line">                .Kp = 1, </div>
<div class="line">                .Ki = 0, </div>
<div class="line">                .Kd = 0, </div>
<div class="line">                .DeadBand = 0, </div>
<div class="line">                .MaxOut = 4000</div>
<div class="line">            }</div>
<div class="line">        }</div>
<div class="line">};</div>
<div class="line"><span class="comment">//注册电机并保存实例指针</span></div>
<div class="line">dji_motor_instance *djimotor = <a class="code hl_function" href="dji__motor_8h.html#ae6a2dbd0d46fc7d80c6e2339aad09969">DJIMotorInit</a>(&amp;config);</div>
</div><!-- fragment --><p>然后在任务中修改电机设定值即可实现控制：</p>
<div class="fragment"><div class="line">DJIMotorSetRef(djimotor, 10);</div>
</div><!-- fragment --><p>前提是已经将<span class="tt"><a class="el" href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65" title="该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率">DJIMotorControl()</a></span>放入实时系统任务当中或以一定d。你也可以单独执行<span class="tt"><a class="el" href="dji__motor_8h.html#a550e8d4dd6e722e66552563550191c65" title="该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率">DJIMotorControl()</a></span>。 </p>
</div></div><!-- contents -->
</div><!-- PageDoc -->
</div><!-- doc-content -->
<div id="page-nav" class="page-nav-panel">
<div id="page-nav-resize-handle"></div>
<div id="page-nav-tree">
<div id="page-nav-contents">
</div><!-- page-nav-contents -->
</div><!-- page-nav-tree -->
</div><!-- page-nav -->
</div><!-- container -->
<!-- start footer part -->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
  <ul>
    <li class="footer">Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.14.0 </li>
  </ul>
</div>
</body>
</html>
